/*
board-games:	An educational framework for board games programming.
    Copyright (C) 2008  Daniel Gutson, FuDePAN
    
    This file is part of board-games.

    board-games is free software: you can redistribute it and/or modify
    it under the terms of the GNU General Public License as published by
    the Free Software Foundation, either version 3 of the License, or
    (at your option) any later version.

    board-games is distributed in the hope that it will be useful,
    but WITHOUT ANY WARRANTY; without even the implied warranty of
    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    GNU General Public License for more details.

    You should have received a copy of the GNU General Public License
    along with this program.  If not, see <http://www.gnu.org/licenses/>.
    
*/

#include "referee_sdk/referee.h"
#include "common_sdk/board.h"
#include "referee_sdk/board_painter.h"
#include "sudoku_internal.h"


class SudokuReferee : public Referee
{    
    Board<SudokuNumber, BOARD_SIZE, BOARD_SIZE> board;
    
    size_t empty_places;
    
    template <class T>
    struct Block
    {
        Coord row0, col0;
        T& board;
        Block(T& board) : board(board), row0(0), col0(0){}
        
        void positionate_from_block_coords(Coord row, Coord col)
        {
            row0 = row*3;
            col0 = col*3;
        }
        
        void positionate_from_board_coords(Coord row, Coord col)
        {
            positionate_from_block_coords(row/3, col/3);
        }
        
        SudokuNumber get(Coord row, Coord col) const
        {
            return board[row0+row][col0+col];
        }
        
        void set(Coord row, Coord col, SudokuNumber number)
        {
            board[row0+row][col0+col] = number;
        }
        
    };
        
    class SudokuPainter : public BoardPainter<SudokuNumber, BOARD_SIZE, BOARD_SIZE>
    {	
    	virtual std::string paint_cell(const SudokuNumber& number, size_t cell_row) const
    	{
        	char num[2] = { 0, 0 };
        	
        	if (number == Empty_Cell)
        	    return " ";
        	else
        	{
            	num[0] = '0' + char(number);
    	        return std::string(num);
	        }
    	}
    	
        virtual std::string paint_inner_horiz_delim(size_t col) const
        {
            return (col % 3) == 2 ? "-" : " ";
        }
    
        virtual std::string paint_inner_vert_delim(size_t row) const
        {
            return (row % 3) == 2 ? "|" : " ";
        }

        virtual std::string paint_upper_mid_sep(size_t col) const
        {
            return (col % 3) == 2 ? "+" : "-";
        }

        virtual std::string paint_lower_mid_sep(size_t col) const
        {
            return paint_upper_mid_sep(col);
        }

        virtual std::string paint_cross_sep(size_t row, size_t col) const
        {
            const bool 
                row_border((row % 3) == 2 ),
                col_border((col % 3) == 2 );

            // inner block?
            if ( row_border && col_border)
                return "+";
            else if (row_border)
                return "-";
            else if (col_border)
                return "|";
            else
                return " ";
        }
    public:
    	SudokuPainter()
    	    : BoardPainter<SudokuNumber, BOARD_SIZE, BOARD_SIZE>(std::cout)
    	{
    	    upper_left = upper_right = lower_left = lower_right = "+";
    	    left_mid_sep = right_mid_sep = outer_vert_delim = "|";
    	    outer_horiz_delim = "-";
    	    cell_height = 1;
    	}
    } const painter;
    
    PlayersProxy* const playersProxy;
    
    virtual bool accepts_number_of_players(size_t players)
    {
        return players == 1;
    }
            
    void show_board() const
    {
    	painter.paint(board);
    }
    
    bool unique_in_block(const Sudoku_Move& move) const
    {
        Block<const Board<SudokuNumber, BOARD_SIZE, BOARD_SIZE> > block(board);

        block.positionate_from_board_coords(move.row, move.col);
        
        Coord row = 0, col = 0; // local coordinates within block
        
        bool unique;
        
        do
        {
            unique = (block.get(row, col) != move.number);
            col++;
            if (col==3)
            {
                col=0;
                row++;
            }
        }
        while(unique && row < 3);
        
        return unique;
    }
    
    bool unique_in_axis(SudokuNumber number, unsigned int step_row, unsigned int step_col, Coord row0, Coord col0) const
    {
        bool unique;
        unsigned int i = 0;
        
        do
        {
            unique = (board[row0+i*step_row][col0+i*step_col] != number);
            i++;
        }
        while (unique && i < BOARD_SIZE);
        
        return unique;
    }
    
    bool unique_in_cross(const Sudoku_Move& move) const
    {
        return 
            unique_in_axis(move.number, 1, 0, Coord(0), move.col) &&
            unique_in_axis(move.number, 0, 1, move.row, Coord(0));
    }
    
    static bool coord_in_range(Coord coord)
    {
        return coord >= 0 && coord < BOARD_SIZE;
    }
    
    static bool number_in_range(SudokuNumber number)
    {
        return number > Empty_Cell && number <= 9;
    }
    
    bool valid_move(const Sudoku_Move& move) const
    {
        bool valid = 
                    coord_in_range(move.row) && 
                    coord_in_range(move.col) &&
                    number_in_range(move.number);

        if (valid)
        {
            valid = (board[move.row][move.col] == Empty_Cell);
            if (valid)
            {
                valid = unique_in_block(move);
                if (valid)
                    valid = unique_in_cross(move);
            }
        }
        return valid;
    }
    
    virtual Winner do_match() throw(PlayerID, const char*) 
    {
        Sudoku_Move move;
        bool valid = true;

        playersProxy->send_board(0, board);
        
        while (empty_places>0 && 
                playersProxy->player_running(0) &&
                valid )
        {
            show_board();
            
            playersProxy->receive_message(0, move);

            valid = valid_move(move);
            
            if (valid)
            {
                board[move.row][move.col] = move.number;
                empty_places--;
                
                if (empty_places > 0)
                    playersProxy->send_message(0, true);
            }
            
        }

        if (valid)        
            show_board();
        else
            std::cout << "Invalid move: [" << move.row << "," << move.col << "] <- " << size_t(move.number) << std::endl;

        // shut down player:
        playersProxy->send_message(0, false);

        return valid ? 1 : 0;
    }
    
    size_t generate_board()
    {
        // generate always the same board:
        struct RandGen
        {
            const SudokuNumber base;
            RandGen() : base(rand() % 9) {}
            //RandGen() : base(8) {}
            SudokuNumber operator()(SudokuNumber original) const
            {
                return (((original-1)+base) % 9) + 1;
            }
        } const r;

        Block<Board<SudokuNumber, BOARD_SIZE, BOARD_SIZE> > block(board);
        size_t ret(0);
        
        block.positionate_from_block_coords(0,0);
        block.set(2, 2, r(1)); ret++;
        
        block.positionate_from_block_coords(0,1);
        block.set(2, 1, r(2)); ret++;
        block.set(1, 2, r(3)); ret++;
        
        block.positionate_from_block_coords(0,2);
        block.set(1, 1, r(8)); ret++;
        block.set(1, 2, r(5)); ret++;

        block.positionate_from_block_coords(1,0);
        block.set(2, 1, r(9)); ret++;
        block.set(1, 2, r(4)); ret++;
        
        block.positionate_from_block_coords(1,1);
        block.set(0, 0, r(5)); ret++;
        block.set(0, 2, r(7)); ret++;
        
        block.positionate_from_block_coords(1,2);
        block.set(1, 0, r(1)); ret++;
        
        block.positionate_from_block_coords(2,0);
        block.set(0, 0, r(5)); ret++;
        block.set(1, 2, r(2)); ret++;
        
        block.positionate_from_block_coords(2,1);
        block.set(1, 1, r(1)); ret++;
        block.set(2, 1, r(4)); ret++;
        
        block.positionate_from_block_coords(2,2);
        block.set(0, 1, r(7)); ret++;
        block.set(0, 2, r(3)); ret++;
        block.set(2, 2, r(9)); ret++;
                    
        return ret;
    }

public:
    SudokuReferee(PlayersProxy* playersProxy)
        : board(Empty_Cell), playersProxy(playersProxy)
    {                        
        empty_places = board.number_of_cells();
        empty_places -= generate_board();
    }
};

Referee* initialize(PlayersProxy* playersProxy)
{
    return new SudokuReferee(playersProxy);
}
